package com.compare.utils;

import java.io.Serializable;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/***
 * @*---------------------------
 * @Description: 数据对比工具类
 * @**---------------------------
 * @author: wulingming
 * @date: 2024年06月26日 09:44
 * @version: v1.0
 * @****---------------------------
 */
public class CompareUtils {


    /**
     * 比较结果
     *
     * @author wulingming
     * @date 2021/11/18 16:18
     **/
    public static class CompareResult<T> implements Serializable {

        private static final long serialVersionUID = 6156436360677906070L;

        /**
         * 删除类型
         */
        private List<T> needDeleteTarget = Collections.emptyList();

        /**
         * 新增类型
         */
        private List<T> needAddSource = Collections.emptyList();

        /**
         * 修改类型
         */
        private List<T> needUpdateSource = Collections.emptyList();

        /**
         * 修改类型：数据相同(可以不用入库)
         * getCompareIsSame有值时:返回结果{@link #needUpdateSameSource}才有数据
         */
        private List<T> needUpdateSameSource = Collections.emptyList();


        /** 修改类型（无需入库）：target中的数据 (加个这个属性是为了方便查看数据) */
        private List<T> infoUpdateTarget = Collections.emptyList();

        /** 修改类型（无需入库）：数据相同。target中的数据 (加个这个属性是为了方便查看数据) */
        private List<T> infoUpdateSameTarget = Collections.emptyList();


        public List<T> getNeedDeleteTarget() {
            return needDeleteTarget;
        }

        public void setNeedDeleteTarget(List<T> needDeleteTarget) {
            this.needDeleteTarget = needDeleteTarget;
        }

        public List<T> getNeedAddSource() {
            return needAddSource;
        }

        public void setNeedAddSource(List<T> needAddSource) {
            this.needAddSource = needAddSource;
        }

        public List<T> getNeedUpdateSource() {
            return needUpdateSource;
        }

        public void setNeedUpdateSource(List<T> needUpdateSource) {
            this.needUpdateSource = needUpdateSource;
        }

        public List<T> getNeedUpdateSameSource() {
            return needUpdateSameSource;
        }

        public void setNeedUpdateSameSource(List<T> needUpdateSameSource) {
            this.needUpdateSameSource = needUpdateSameSource;
        }

        public List<T> getInfoUpdateTarget() {
            return infoUpdateTarget;
        }

        public void setInfoUpdateTarget(List<T> infoUpdateTarget) {
            this.infoUpdateTarget = infoUpdateTarget;
        }

        public List<T> getInfoUpdateSameTarget() {
            return infoUpdateSameTarget;
        }

        public void setInfoUpdateSameTarget(List<T> infoUpdateSameTarget) {
            this.infoUpdateSameTarget = infoUpdateSameTarget;
        }
    }

    /**
     * 比较两批数据，标记增删改。（一般用于全量更新表数据。用于增量更新时只看新增、修改返回结果即可）。数据sourse、target比较时，以sourse集合作为标准，使能够得出让souse、target集合数据保持一致的结果。
     * <p>
     * 1：传入实体没有，数据库有：删除操作
     * 2；传入实体有，数据库有：修改操作
     * 3：传入实体有，数据库没有：新增操作
     *
     * @param sourse           数据源（一般是待入库的数据）
     * @param target           目标数据（一般是数据库的数据）
     * @param getUniqueFlag    获取唯一标识
     * @param getCompareIsSame (一般用于对数据库完全相同的数据不做修改，避免触发无用的修改动作)获取对象数据字符串形式，用于比较数据是否相同（getCompareIsSame有值时:返回结果needUpdateSameSource有数据）。比如：除了对比主键是否一致，还要对比各个字段是否一致。
     *                         比如：member -> JSON.toJSONString(Arrays.asList(member.getName(), member.getSelf()))。不建议使用将对象直接输出json，字段排序有可能不一致
     * @return 比较结果
     * @author wulingming
     */
    public static <T> CompareResult<T> compare(Collection<T> sourse, Collection<T> target, Function<T, String> getUniqueFlag, Function<T, String> getCompareIsSame) {
        if (sourse == null) {
            sourse = Collections.emptyList();
        }
        if (target == null) {
            target = Collections.emptyList();
        }
        if (getUniqueFlag == null) {
            throw new IllegalArgumentException("getUniqueFlag参数不能为空！");
        }


        //数据源去重 改为由调用者去重...
        //        sourse = sourse.stream().filter(Objects::nonNull).collect(collectingAndThen(toCollection(() -> new TreeSet<>(Comparator.comparing(getUniqueFlag))), ArrayList::new));

        //“数据源”唯一标识 set比list查找速度更快。
        Set<String> sourUniqueFlags = sourse.stream().map(getUniqueFlag).collect(Collectors.toSet());
        //map中 key: “目标数据”唯一标识。  value: “目标数据”
        Map<String, T> targetUniqueFlagMap = target.stream().filter(Objects::nonNull).collect(Collectors.toMap(getUniqueFlag, o -> o, (o, o2) -> o));

        // 1：传入实体没有，数据库有：删除操作(id有值)
        List<T> needDeleteTarget = target.stream().filter(Objects::nonNull).filter(t -> !sourUniqueFlags.contains(getUniqueFlag.apply(t))).collect(Collectors.toList());

        // 3：传入实体有，数据库没有：新增操作(id无值，需要调用者自己设置)
        List<T> needAddSource = sourse.stream().filter(t -> !targetUniqueFlagMap.containsKey(getUniqueFlag.apply(t))).collect(Collectors.toList());

        List<T> needUpdateSource;
        List<T> needUpdateSameSource;

        List<T> infoUpdateTarget;
        List<T> infoUpdateSameTarget;

        if (getCompareIsSame == null) {
            //只检查唯一标识是否存在，不检测数据是否相同

            //2；传入实体有，数据库有：修改操作
            needUpdateSource = sourse.stream().filter(t -> targetUniqueFlagMap.containsKey(getUniqueFlag.apply(t))).collect(Collectors.toList());

            if (!needUpdateSource.isEmpty()) {
                infoUpdateTarget = needUpdateSource.stream().map(dTO -> targetUniqueFlagMap.get(getUniqueFlag.apply(dTO))).collect(Collectors.toList());
            } else {
                infoUpdateTarget = Collections.emptyList();
            }

            needUpdateSameSource = Collections.emptyList();
            infoUpdateSameTarget = Collections.emptyList();
        } else {
            //检测唯一标识，并且比较数据是否相同

            //2；传入实体有，数据库有：修改操作（数据不同）
            needUpdateSource = sourse.stream().filter(t -> {
                String uniqueFlag = getUniqueFlag.apply(t);
                return targetUniqueFlagMap.containsKey(uniqueFlag) && Comparator.comparing(getCompareIsSame).compare(t, targetUniqueFlagMap.get(uniqueFlag)) != 0;
            }).collect(Collectors.toList());

            if (!needUpdateSource.isEmpty()) {
                infoUpdateTarget = needUpdateSource.stream().map(dTO -> targetUniqueFlagMap.get(getUniqueFlag.apply(dTO))).collect(Collectors.toList());
            } else {
                infoUpdateTarget = Collections.emptyList();
            }

            //2；传入实体有，数据库有：修改操作（数据相同）
            needUpdateSameSource = sourse.stream().filter(t -> {
                String uniqueFlag = getUniqueFlag.apply(t);
                boolean b = targetUniqueFlagMap.containsKey(uniqueFlag);
                if (b) {
                    b = Comparator.comparing(getCompareIsSame).compare(t, targetUniqueFlagMap.get(uniqueFlag)) == 0;
                    if (!b) {
                        //这块逻辑可以和“//2；传入实体有，数据库有：修改操作（数据不同）”合并到一起
//                        log.debug("对比过程：匹配但不一致数据：uniqueFlag={},sourceStr={} ========= targetStr={}", uniqueFlag, getCompareIsSame.apply(t), getCompareIsSame.apply(targetUniqueFlagMap.get(uniqueFlag)));
                    }
                }

                return b;
            }).collect(Collectors.toList());

            if (!needUpdateSameSource.isEmpty()) {
                infoUpdateSameTarget = needUpdateSameSource.stream().map(dTO -> targetUniqueFlagMap.get(getUniqueFlag.apply(dTO))).collect(Collectors.toList());
            } else {
                infoUpdateSameTarget = Collections.emptyList();
            }

        }

        CompareResult<T> result = new CompareResult<>();
        result.setNeedDeleteTarget(needDeleteTarget);

        result.setNeedAddSource(needAddSource);
        result.setNeedUpdateSource(needUpdateSource);
        result.setNeedUpdateSameSource(needUpdateSameSource);

        result.setInfoUpdateTarget(infoUpdateTarget);
        result.setInfoUpdateSameTarget(infoUpdateSameTarget);

        return result;
    }

    /**
     * 比较两批数据，标记增删改。（一般用于全量更新表数据。用于增量更新时只看新增、修改返回结果即可）。数据sourse、target比较时，以sourse集合作为标准，使能够得出让souse、target集合数据保持一致的结果。
     * <p>
     * 1：传入实体没有，数据库有：删除操作
     * 2；传入实体有，数据库有：修改操作
     * 3：传入实体有，数据库没有：新增操作
     *
     * @param sourse        数据源（一般是待入库的数据）
     * @param target        目标数据（一般是数据库的数据）
     * @param getUniqueFlag 唯一标识
     * @return 比较结果(返回结果needUpdateSameSource没有数据)
     * @author wulingming
     */
    public static <T> CompareResult<T> compare(Collection<T> sourse, Collection<T> target, Function<T, String> getUniqueFlag) {
        return compare(sourse, target, getUniqueFlag, null);
    }

    //测试工具类
    //    public static void main(String[] args) {
    //        Member m0 = new Member();
    //        m0.setName("m1");
    //        Member m1 = new Member();
    //        m1.setName("m1");
    //        Member m2 = new Member();
    //        m2.setName("m2");
    //        Member m3 = new Member();
    //        m3.setName("m3,\"\"\"\"\",,, ");
    //
    //        Member tm2 = new Member();
    //        tm2.setName("m2");
    //        Member tm3 = new Member();
    //        tm3.setName("m3,\"\"\"\"\",,, ");
    //        tm3.setSelf(true);
    //        Member tm4 = new Member();
    //        tm4.setName("m4,\"\"\"\"\",,, ");
    //
    //        List<Member> members1 = Arrays.asList(m0, m1, m2, m3, null);
    //        List<Member> members2 = Arrays.asList(tm2, tm3, tm4, null);
    //
    //        CompareResult<Member> compare = compare(members1, members2, MemberEntity::getName, member -> JSON.toJSONString(Arrays.asList(member.getName(), member.getSelf())));
    //
    //        System.out.println(JSON.toJSONString(compare));
    //
    //    }


}